package se.stabby.undersok.model.statics;

import com.google.common.base.Preconditions;
import se.stabby.undersok.model.*;

import java.util.*;
import java.util.function.BiFunction;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
 * User: per.segersten
 * Date: 2014-06-01
 */
public class KategoriUndersökningsBuilder {

    private String titel;
    private String beskrivning;
    private Map<Character, List<String>> kategorier = new HashMap<>();
    private List<String> frågor = new LinkedList<>();
    private List<List<String>> svarsalternativen = new LinkedList<>();
    private List<List<Character>> svarskategorier = new LinkedList<>();
    private Metadata metadata;
    private Long id;

    public static KategoriUndersökningsBuilder create() {
        return new KategoriUndersökningsBuilder();
    }

    public KategoriUndersökningsBuilder medTitel(String titel) {
        this.titel = titel;
        return this;
    }

    public KategoriUndersökningsBuilder medBeskrivning(String beskrivning) {
        this.beskrivning = beskrivning;
        return this;
    }

    public KategoriUndersökningsBuilder medSvarskategori(char a, String ... analyser) {
        kategorier.put(a, Arrays.asList(analyser));
        return this;
    }

    public KategoriUndersökningsBuilder medFråga(String fråga) {
        frågor.add(fråga);
        svarsalternativen.add(new LinkedList<>());
        svarskategorier.add(new LinkedList<>());
        return this;
    }

    public KategoriUndersökningsBuilder medSvar(char kategori, String svarsalternativ) {
        Preconditions.checkArgument(kategorier.containsKey(kategori), "Okänd kategori: " + kategori);
        Preconditions.checkArgument(svarsalternativen.size() == frågor.size(), "Svar utan fråga");
        Preconditions.checkArgument(svarskategorier.size() == frågor.size(), "Svar utan fråga");

        int index = svarsalternativen.size() - 1;
        svarsalternativen.get(index).add(svarsalternativ);
        svarskategorier.get(index).add(kategori);

        return this;
    }

    public KategoriUndersökningsBuilder medMetaData(Metadata metadata) {
        this.metadata = metadata;
        return this;
    }

    public KategoriUndersökningsBuilder medId(long id) {
        this.id = id;
        return this;
    }

    public Undersökning buildUndersökning() {
        verifyFields();
        List<Fråga> f = new ArrayList<>(frågor.size());
        for(int i=0;i<frågor.size();i++){
            f.add(new Fråga(new FrågaId(id*1000 + 1), frågor.get(i), svarsalternativen.get(i), metadata));
        }
        return new Undersökning(new UndersökningsId(id), titel, beskrivning, f, metadata);
    }

    private void verifyFields() {
        Preconditions.checkNotNull(this.titel);
        Preconditions.checkNotNull(this.beskrivning);
        Preconditions.checkNotNull(this.metadata);
        Preconditions.checkNotNull(this.id);
        Preconditions.checkArgument(frågor.size() == svarsalternativen.size(), String.format("%s != %s", kategorier.size(), svarsalternativen.size()));
        Preconditions.checkArgument(frågor.size() == svarskategorier.size(), String.format("%s != %s", kategorier.size(), svarskategorier.size()));
        Preconditions.checkArgument(svarskategorier.stream().allMatch(s->s.size()>0));
        Preconditions.checkArgument(svarsalternativen.stream().allMatch(s->s.size()>0));
    }

    public Tolkare buildTolkare() {
        verifyFields();
        Map<FrågaId, List<Character>> s = new HashMap<>();
        for(int i=0;i<svarskategorier.size();i++){
            s.put(new FrågaId(id*1000 + 1), svarskategorier.get(i));
        }
        return new KategoriTolkare(this.kategorier, s);
    }
}

class Zip {
    public static<A, B, C> Stream<C> zip(Stream<? extends A> a,
                                         Stream<? extends B> b,
                                         BiFunction<? super A, ? super B, ? extends C> zipper) {
        Objects.requireNonNull(zipper);
        @SuppressWarnings("unchecked")
        Spliterator<A> aSpliterator = (Spliterator<A>) Objects.requireNonNull(a).spliterator();
        @SuppressWarnings("unchecked")
        Spliterator<B> bSpliterator = (Spliterator<B>) Objects.requireNonNull(b).spliterator();

        // Zipping looses DISTINCT and SORTED characteristics
        int both = aSpliterator.characteristics() & bSpliterator.characteristics() &
                ~(Spliterator.DISTINCT | Spliterator.SORTED);
        int characteristics = both;

        long zipSize = ((characteristics & Spliterator.SIZED) != 0)
                ? Math.min(aSpliterator.getExactSizeIfKnown(), bSpliterator.getExactSizeIfKnown())
                : -1;

        Iterator<A> aIterator = Spliterators.iterator(aSpliterator);
        Iterator<B> bIterator = Spliterators.iterator(bSpliterator);
        Iterator<C> cIterator = new Iterator<C>() {
            @Override
            public boolean hasNext() {
                return aIterator.hasNext() && bIterator.hasNext();
            }

            @Override
            public C next() {
                return zipper.apply(aIterator.next(), bIterator.next());
            }
        };

        Spliterator<C> split = Spliterators.spliterator(cIterator, zipSize, characteristics);
        return (a.isParallel() || b.isParallel())
                ? StreamSupport.stream(split, true)
                : StreamSupport.stream(split, false);
    }
}